﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Irony.Parsing;
using Irony.Ast;

namespace Skyrim
{
    [Language("Papyrus", "0.1", "Grammar for Skyrim Scripts")]
    public class PapyrusGrammar : Grammar
    {
        public PapyrusGrammar()
            : base(false)
        {
            #region Commentaires
            var comment = new CommentTerminal("comment", ";", "\n", "\r");
            var blockComment = new CommentTerminal("blockComment", ";/", "/;");
            var docComment = new CommentTerminal("docComment", "{", "}");
            NonGrammarTerminals.Add(comment);
            NonGrammarTerminals.Add(blockComment);
            NonGrammarTerminals.Add(docComment);
            #endregion

            var cast = new NonTerminal("cast"); 
            var headerLine = new NonTerminal("headerLine"); 
            var variable = new NonTerminal("variable"); 
            var imports = new NonTerminal("imports"); 
            var type = new Terminal("type"); 
            var identifier = new NonTerminal("identifier");   
            var assignmentOp = new NonTerminal("assignmentOp", "assignment operator");  
            var comparaisonOp = new NonTerminal("comparaison_operator");
            var assignmentStmt = new NonTerminal("assignmentStmt");//, typeof(AssigmentNode));  
            var parameter = new NonTerminal("parameter");   
            var parameters = new NonTerminal("parameters");  
            var globalFnCall = new NonTerminal("global_fnCall");  
            var nonGlobalFnCall = new NonTerminal("nonGlobal_fnCall");  
            var functionBlock = new NonTerminal("functionBlock");  
            var functionHeader = new NonTerminal("functionHeader");  
            var constant = new Terminal("constant");  
            var andOp = ToTerm("&&");  
            var orOp = ToTerm("||");  
            var letter = new RegexBasedTerminal("letter", "[a-z]");  
            var digit = new NumberLiteral("digit");  
            var nativeFlag = ToTerm("native");  
            var hiddenFlag = ToTerm("hidden");  
            var globalFlag = ToTerm("global");  
            var conditionalFlag = ToTerm("conditional");  
            var flags = conditionalFlag | nativeFlag | hiddenFlag | globalFlag;  
            var intType = ToTerm("int");  
            var expression = new NonTerminal("expression");  
            var andExpression = new NonTerminal("and_expression");  
            var boolExpression = new NonTerminal("bool_expression");  
            var addExpression = new NonTerminal("add_expression");  
            var multExpression = new NonTerminal("mult_expression");  
            var unaryExpression = new NonTerminal("unary_expression");  
            var castAtom = new NonTerminal("cast_atom");  
            var dotAtom = new NonTerminal("dot_atom");  
            var arrayAtom = new NonTerminal("array_atom");  
            var arrayFundId = new NonTerminal("array_fund_id");  
            var atom = new NonTerminal("atom");  
            var funcId = new NonTerminal("func_id");  
            var function = new NonTerminal("function");  
            var statement = new NonTerminal("statement");
            var ifStatement = new NonTerminal("ifStatement");
            var whileStatement = new NonTerminal("whileStatement");
            var defineStatement = new NonTerminal("defineStatement");  
            var assignStatement = new NonTerminal("assignStatement");  
            var leftValue = new NonTerminal("leftValue");
            var functionCall = globalFlag | nonGlobalFnCall; //new NonTerminal("function_call");

            cast.Rule = expression + "as" + type;
            parameter.Rule = type + identifier + ((ToTerm("=") + constant) | Empty);
            parameters.Rule = parameter + MakeStarRule(parameters, "," + parameter);
            imports.Rule = "Import" + identifier;

            // <variable definition> ::= <type> <identifier> ['=' <constant>] (<flags>)*
            variable.Rule = type + identifier + MakePlusRule(variable, ToTerm("=") + constant) + MakeStarRule(variable, flags);

            // <header line>
            // (<import>|<variable definition>|<property definition>|<state definition>|<function definition>|<event definition>)*
            // <Header Line> ::= 'ScriptName' <identifier> ['extends' <identifier>] (<flags>)*
            headerLine.Rule = ToTerm("scriptname") + identifier + ((ToTerm("extends") + identifier) | Empty) + MakeStarRule(headerLine, flags);



            // <identifier> ::= (<letter> | '_') (<letter> | <digit> | '_')*
            identifier.Rule = (letter | ToTerm("_")) + MakeStarRule(identifier, letter | digit | ToTerm("_"));
            assignmentOp.Rule = ToTerm("=") | "+=" | "-=" | "*=" | "/=" | "%=";
            comparaisonOp.Rule = ToTerm("==") | "!=" | ">=" | "<=" | "<" | ">";


            //Expr.Rule = Term | UnExpr | BinExpr | PostFixExpr;
            assignmentStmt.Rule = identifier + assignmentOp + expression;

            #region Functions
            // <function header> ::= [<type>] 'Function' <identifier> '(' [<parameters>] ')' ('global' | 'native')* <flags>*
            functionHeader.Rule = (type | Empty) + "function" + identifier + "(" +
                                      (parameters | Empty) + ")" +
                                      MakeStarRule(functionHeader, globalFlag | nativeFlag) + MakeStarRule(functionHeader, flags);

            functionBlock.Rule = MakeStarRule(functionBlock, statement);

            globalFnCall.Rule = MakePlusRule(globalFnCall, identifier + ".") + identifier + "(" + parameters + ")";

            nonGlobalFnCall.Rule = MakePlusRule(nonGlobalFnCall, expression + ".") + identifier + "(" + parameters + ")";
            //<function> ::= <function header> [<function block> 'endFunction']
            function.Rule = functionHeader + ((functionBlock + "endfunction") | Empty);
            #endregion

            #region Expressions Rules
            //<expression>       ::= <and expression> ('||' <and expression>)*
            expression.Rule = andExpression + MakeStarRule(expression, null, "||" + andExpression);
            //<and expression>   ::= <bool expression> ('&&' <bool expression>)*
            andExpression.Rule = boolExpression + MakeStarRule(andExpression, null, "&&" + boolExpression);
            //<bool expression>  ::= <add expression> (<comparison operator> <add expression>)*
            boolExpression.Rule = addExpression + MakeStarRule(boolExpression, null, comparaisonOp + addExpression);
            //<add expression>   ::= <mult expression> (('+' | '-') <mult expression>)*
            addExpression.Rule = multExpression +
                                  MakeStarRule(addExpression, null, (ToTerm("+") | "-") + multExpression);
            //<mult expression>  ::= <unary expression> (('*' | '/' | '%') <unary expression>)*
            multExpression.Rule = unaryExpression + MakeStarRule(multExpression, null, 
                                                        (ToTerm("*") | "/" | "%") + unaryExpression);
            //<unary expression> ::= ['-' | '!'] <cast atom>
            unaryExpression.Rule = ((ToTerm("-") | "!") | Empty) + castAtom;
            //<cast atom>        ::= <dot atom> ['as' <type>]
            castAtom.Rule = dotAtom +  (("as" + type) | Empty);
            //<dot atom>         ::= (<array atom> ('.' <array func or id>)*) | <constant>
            dotAtom.Rule = arrayAtom + MakeStarRule(dotAtom, null, ToTerm(".") + arrayFundId) | constant;
            //<array atom>       ::= <atom> ['[' <expression> ']']
            arrayAtom.Rule = atom + ((ToTerm("[") + expression + ToTerm("]")) | Empty);
            //<atom>             ::= ('(' <expression> ')') | ('new' <type> '[' <int> ']') | <func or id>
            atom.Rule = (ToTerm("(") + expression + ToTerm(")"))
                        | (ToTerm("new") + type + ((ToTerm("[") + digit + ToTerm("]")) | Empty)) 
                        | funcId;
            //<array func or id> ::= <func or id> ['[' <expression> ']']
            arrayFundId.Rule = funcId + ((ToTerm("[") + expression + ToTerm("]"))|Empty);
            //<func or id>       ::= <function call> | <identifier> | 'length'
            funcId.Rule = functionCall | identifier | ToTerm("length");
            #endregion

            #region Statements Rules
            //<define statement> ::= <type> <identifier> ['=' <expression>]
            defineStatement.Rule = type + identifier + ((ToTerm("=") + expression) | Empty);
            //<l-value>          ::= ([<expression> '.'] <identifier>) (<expression> '[' <expression> ']')
            leftValue.Rule = (expression + ToTerm("."))|Empty + (expression + ToTerm("[") + expression + ToTerm("]"));

            assignStatement.Rule = (leftValue + ToTerm("=") + expression) | (leftValue + ToTerm("+=") + expression) |
                        (leftValue + ToTerm("-=") + expression) |
                        (leftValue + ToTerm("*=") + expression) |
                        (leftValue + ToTerm("/=") + expression) |
                        (leftValue + ToTerm("%=") + expression);
            ifStatement.Rule = "if" + expression + MakeStarRule(ifStatement, null, statement) +
                               (("elseif" + expression + MakeStarRule(ifStatement, null, statement)) | Empty) +
                               (("else" + expression + MakeStarRule(ifStatement, null, statement)) | Empty) + "endif";
            whileStatement.Rule = "while" + expression + MakeStarRule(whileStatement, null, statement) + "endwhile";
            statement.Rule = (defineStatement | assignStatement | ifStatement | whileStatement | expression | ToTerm("return") + (expression | Empty));
            #endregion

            MarkPunctuation("(", ")");
            MarkPunctuation("!");
            RegisterBracePair("(", ")");
            RegisterBracePair("[", "]");

            

            RegisterOperators(1, Associativity.Neutral, ToTerm("."));
            RegisterOperators(2, Associativity.Neutral, ToTerm("as"));
            RegisterOperators(3, Associativity.Neutral, ToTerm("-") | "!");
            RegisterOperators(4, Associativity.Neutral, ToTerm("*") | "/" | "%");
            RegisterOperators(5, Associativity.Neutral, ToTerm("+") | "-");
            RegisterOperators(6, Associativity.Neutral, comparaisonOp);
            RegisterOperators(7, Associativity.Neutral, andOp);
            RegisterOperators(8, Associativity.Neutral, orOp);
            RegisterOperators(9, Associativity.Neutral, assignmentOp);

            var script = new NonTerminal("Script");
            script.Rule = headerLine + MakeStarRule(script, null, statement);
            Root = script;

            LanguageFlags = LanguageFlags.CreateAst | LanguageFlags.NewLineBeforeEOF;
        }
    }
}
